package com.fit.exercise.msgp2p;

import java.nio.ByteBuffer;

public class Packet {
	public static final short OPTION_DATA_LENGTH = 8;

	public static final byte TYPE_CHAT_MSG = 0;
	public static final byte TYPE_ADD_FRIEND_MSG = 1;
	
	public static final byte TYPE_GROUP_CHAT_REQ = 2;
	public static final byte TYPE_GROUP_CHAT_RES = 3;
	public static final byte TYPE_GROUP_CHAT_MSG = 4;
	public static final byte TYPE_GROUP_CHAT_ADD_MORE = 5;
	

	public static final byte FLAG_DATA = 0;
	public static final byte FLAG_ACK = 1;


	private Byte type;

	private Byte flag;

	private Short headerLength;

	private Short packetLength;

	private Short checkSum;

	private Integer sequenceNumber;

	private Integer acknowledgeNumber;

	private Integer listenPort;

	private byte[] optionData;

	private byte[] data;

	public byte[] getBytes() {
		// Calculate packet length
		packetLength = (short) (headerLength + data.length);
		// Put header
		ByteBuffer byteBuffer = ByteBuffer.allocate(packetLength);
		byteBuffer.clear();
		byteBuffer.put(type).put(flag).putShort(headerLength)
				.putShort(packetLength).putShort(checkSum)
				.putInt(sequenceNumber).putInt(acknowledgeNumber)
				.putInt(listenPort);
		byteBuffer.put(getOptionData());
		byteBuffer.put(data);

		return byteBuffer.array();
	}

	public Packet(byte[] recvData) {
		ByteBuffer byteBuffer = ByteBuffer.wrap(recvData);
		type = byteBuffer.get();
		flag = byteBuffer.get();

		headerLength = byteBuffer.getShort();

		packetLength = byteBuffer.getShort();

		checkSum = byteBuffer.getShort();

		sequenceNumber = byteBuffer.getInt();

		acknowledgeNumber = byteBuffer.getInt();

		listenPort = byteBuffer.getInt();

		optionData = new byte[OPTION_DATA_LENGTH];
		for (int i = 0; i < OPTION_DATA_LENGTH; i++) {
			optionData[i] = byteBuffer.get();
		}

		data = new byte[packetLength - headerLength];
		for (int i = 0; i < (packetLength - headerLength); i++) {
			data[i] = byteBuffer.get();
		}
	}

	public Packet() {
	}

	public Packet(Builder builder) {
		this.type = builder.type;
		this.flag = builder.flag;
		this.checkSum = builder.checkSum;
		this.sequenceNumber = builder.sequenceNumber;
		this.acknowledgeNumber = builder.acknowledgeNumber;
		this.headerLength = builder.headerLength;
		this.packetLength = (short) (builder.headerLength + builder.data.length);
		this.listenPort = builder.listenPort;

		// Option data length must be OPTION_DATA_LENGTH
		if (builder.optionData == null
				|| builder.optionData.length != OPTION_DATA_LENGTH) {
			this.optionData = new byte[OPTION_DATA_LENGTH];
		} else {
			this.optionData = builder.optionData;
		}
		this.data = builder.data;
	}

	public static class Builder {
		private Byte type;

		private Byte flag;

		private Short headerLength = (short) (20 + OPTION_DATA_LENGTH);

		private Short checkSum = 1;

		private Integer sequenceNumber = 1;

		private Integer acknowledgeNumber = 1;

		private Integer listenPort;

		private byte[] optionData;

		private byte[] data;

		public Builder(Byte type, Byte flag, Integer listenPort, byte[] data) {
			super();
			this.type = type;
			this.flag = flag;
			this.listenPort = listenPort;
			this.data = data;
		}

		public Builder headerLength(Short headerLength) {
			this.headerLength = headerLength;
			return this;
		}

		public Builder checkSum(Short checkSum) {
			this.checkSum = checkSum;
			return this;
		}

		public Builder sequenceNumber(Integer sequenceNumber) {
			this.sequenceNumber = sequenceNumber;
			return this;
		}

		public Builder acknowledgeNumber(Integer acknowledgeNumber) {
			this.acknowledgeNumber = acknowledgeNumber;
			return this;
		}

		public Builder optionData(byte[] optionData) {
			this.optionData = optionData;
			return this;
		}

		public Packet build() {
			return new Packet(this);
		}
	}

	public Byte getType() {
		return type;
	}

	public void setType(Byte type) {
		this.type = type;
	}

	public Byte getFlag() {
		return flag;
	}

	public void setFlag(Byte flag) {
		this.flag = flag;
	}

	public Short getHeaderLength() {
		return headerLength;
	}

	public void setHeaderLength(Short headerLength) {
		this.headerLength = headerLength;
	}

	public Short getPacketLength() {
		return packetLength;
	}

	public void setPacketLength(Short packetLength) {
		this.packetLength = packetLength;
	}

	public Short getCheckSum() {
		return checkSum;
	}

	public void setCheckSum(Short checkSum) {
		this.checkSum = checkSum;
	}

	public Integer getSequenceNumber() {
		return sequenceNumber;
	}

	public void setSequenceNumber(Integer sequenceNumber) {
		this.sequenceNumber = sequenceNumber;
	}

	public Integer getAcknowledgeNumber() {
		return acknowledgeNumber;
	}

	public void setAcknowledgeNumber(Integer acknowledgeNumber) {
		this.acknowledgeNumber = acknowledgeNumber;
	}

	public Integer getListenPort() {
		return listenPort;
	}

	public void setListenPort(Integer listenPort) {
		this.listenPort = listenPort;
	}

	public byte[] getData() {
		return data;
	}

	public void setData(byte[] data) {
		this.data = data;
	}

	@Override
	public String toString() {
		return "Packet [type=" + type + ", flag=" + flag + ", headerLength="
				+ headerLength + ", packetLength=" + packetLength
				+ ", checkSum=" + checkSum + ", sequenceNumber="
				+ sequenceNumber + ", acknowledgeNumber=" + acknowledgeNumber
				+ ", listenPort=" + listenPort + ", data=" + data + "]";
	}

	public byte[] getOptionData() {
		return optionData;
	}

	public void setOptionData(byte[] optionData) {
		this.optionData = optionData;
	}

}